/*
 * TownSquare embed stylesheet.
 *
 * Scoped to the widget mount point so external host pages are not restyled.
 * TownSquare host pages also load page.css for full-page chrome.
 */
@import url("./tokens.css");

#townsquare-root {
  color-scheme: light;
  font-family: var(--font);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
}

#townsquare-root[data-townsquare-theme="auto"] {
  color-scheme: light dark;
}

#townsquare-root[data-townsquare-theme="dark"] {
  color-scheme: dark;
}

#townsquare-root,
#townsquare-root * {
  box-sizing: border-box;
}

/* ============================================================================
   LAYOUT SYSTEM

   The widget has three independent vertical zones:

   ┌─────────────────────────┐
   │    Controls (UI)         │  z-index: overlay
   │                         │
   │ ┌─────────────────────┐ │
   │ │  STAGE (sky)        │ │  180px (flex in expanded mode)
   │ │  [scene + figures]  │ │
   │ │                     │ │
   │ └─────────────────────┘ │
   │ ───────────────────────── │  ground line (1px border-top)
   │       GROUND (nameplate) │  40px
   │ ─────────────────────────┤
   │    ACTION-ZONE (toolbar) │  60px
   │     [composer + actions] │
   └─────────────────────────┘

   Key coordinate systems:
   - Props/figures position relative to stage: bottom: 0 = ground line
   - Controls/toolbar position absolutely within zones
   - Ground line: stage bottom edge = --ts-zone-ground-y

   ============================================================================ */

.townsquare {
  /* Zone heights: stage (flexible in expanded), ground (fixed nameplate band),
     action-zone (fixed toolbar area). */
  --ts-zone-stage-height: 180px;
  /* 44px, not a round 40: the self nameplate needs top offset (9px) + badge
     height (~34px) = 43px of room, or it clips into the action-zone below. */
  --ts-zone-ground-height: 44px;
  --ts-zone-action-height: 60px;

  /* Total height reserve (stage + action, ground not counted as it overlays stage). */
  --ts-layout-height-reserve: calc(var(--ts-zone-stage-height) + var(--ts-zone-action-height));

  /* Ground line coordinate system: props bottom-anchor to this plane. */
  --ts-ground-level: 0px;  /* Anchored to stage bottom edge (relative to stage) */

  /* Prop positioning details. */
  --ts-bench-seat-height: 10px;  /* Bench plank centerline offset from ground plane */

  /* UI spacing: control/toolbar/status positioning within zones. */
  --ts-controls-inset: 8px;      /* Controls panel from top-right */
  --ts-controls-gap: 6px;        /* Gap between control buttons */
  --ts-status-inset: 8px;        /* Status row from top-left */
  --ts-toolbar-inset: 10px;      /* Toolbar horizontal margin in action-zone */
  --ts-toolbar-bottom: 7px;      /* Toolbar vertical position from action-zone bottom */
  --ts-toolbar-gap: 8px;         /* Gap between toolbar items */

  /* Z-index layers (semantic naming). */
  --ts-z-base: 1;
  --ts-z-ui: 2147483001;         /* Controls, actions, toolbar: above scene */
  --ts-z-ui-above: 2147483002;   /* Toolbar (above floating actions) */

  position: relative;
  min-height: var(--ts-layout-height-reserve);
  overflow: hidden;
  background: transparent;
}

.townsquare__controls {
  position: absolute;
  z-index: var(--ts-z-ui);
  top: var(--ts-controls-inset);
  right: 4px;
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: var(--ts-controls-gap);
}

/* Unified button base: shared styles for all buttons */
.townsquare__button {
  display: inline-grid;
  place-items: center;
  margin: 0;
  padding: 0;
  border: 1px solid color-mix(in oklab, var(--hair-2) 76%, transparent);
  border-radius: 8px;
  background: color-mix(in oklab, var(--surface) 78%, transparent);
  color: var(--ink-soft);
  box-shadow: 0 4px 12px -10px rgba(42, 41, 38, 0.38);
  backdrop-filter: blur(8px);
  cursor: pointer;
}

.townsquare__button:hover,
.townsquare__button:focus-visible,
.townsquare__button--active {
  color: var(--ink);
  border-color: var(--hair-2);
  background: var(--surface);
}

.townsquare__button:focus-visible {
  outline: 3px solid color-mix(in oklab, var(--you) 48%, transparent);
  outline-offset: 2px;
}

.townsquare__button:disabled {
  opacity: var(--ts-opacity-disabled);
  cursor: default;
}

/* Small buttons: 30×30 (expand, help, edit) */
.townsquare__button--sm {
  width: 30px;
  height: 30px;
}

.townsquare__button--sm svg {
  width: 16px;
  height: 16px;
}

/* Medium buttons: 42×42 (jump, high-five actions) */
.townsquare__button--md {
  width: 42px;
  height: 42px;
  box-shadow: 0 8px 20px -14px rgba(42, 41, 38, 0.42);
  background: color-mix(in oklab, var(--surface) 84%, transparent);
  font: 700 18px/1 var(--font);
}

.townsquare__button--md svg {
  width: 19px;
  height: 19px;
}

.townsquare__enable-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  width: 36px;
  height: 30px;
  margin: 0;
  cursor: pointer;
}

.townsquare__enable-toggle-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.townsquare__enable-toggle-track {
  position: relative;
  display: block;
  width: 36px;
  height: 20px;
  border: 1px solid color-mix(in oklab, var(--hair-2) 76%, transparent);
  border-radius: 999px;
  background: color-mix(in oklab, var(--surface) 78%, transparent);
  box-shadow: 0 4px 12px -10px rgba(42, 41, 38, 0.38);
  backdrop-filter: blur(8px);
  transition: background 0.15s ease, border-color 0.15s ease;
}

.townsquare__enable-toggle-track::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 3px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--ink-soft);
  transform: translateY(-50%);
  transition: transform 0.15s ease, background 0.15s ease;
}

.townsquare__enable-toggle-input:checked + .townsquare__enable-toggle-track {
  background: color-mix(in oklab, var(--you) 32%, var(--surface));
  border-color: color-mix(in oklab, var(--you) 48%, var(--hair-2));
}

.townsquare__enable-toggle-input:checked + .townsquare__enable-toggle-track::after {
  transform: translate(16px, -50%);
  background: var(--you-deep);
}

.townsquare__enable-toggle-input:focus-visible + .townsquare__enable-toggle-track {
  outline: 3px solid color-mix(in oklab, var(--you) 48%, transparent);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .townsquare__enable-toggle-track,
  .townsquare__enable-toggle-track::after {
    transition: none;
  }
}

/* ACTION-ZONE: toolbar and action buttons area at widget bottom.
   Positioned relative so toolbar anchors within it (shows zone background).
   Min-height: toolbar ~42px + bottom inset ~7px + safety margin ~2px ≈ 60px. */
.townsquare__action-zone {
  position: relative;
  background: transparent;
  min-height: var(--ts-zone-action-height);
}

#townsquare-root[data-townsquare-surface] .townsquare__action-zone {
  background: var(--action-zone-fill, var(--page));
}

/* Action buttons: jump and high-five (desktop floating position, hide on touch). */
.townsquare__actions {
  position: absolute;
  z-index: var(--ts-z-ui);
  right: 8px;
  bottom: 12px;
  display: flex;
  gap: var(--ts-toolbar-gap);
}


.townsquare__help-scrim {
  position: fixed;
  inset: 0;
  z-index: 2147483646;
  display: grid;
  place-items: center;
  padding: 24px;
  background: color-mix(in oklab, var(--ink) 34%, transparent);
  backdrop-filter: blur(6px);
}

.townsquare__help-scrim[hidden] {
  display: none;
}

.townsquare__help-panel {
  width: min(320px, calc(100vw - 48px));
  max-height: min(80vh, 520px);
  overflow: auto;
  padding: 16px;
  border: 1px solid var(--hair-2);
  border-radius: 12px;
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--tray-shadow);
}

.townsquare__help-panel strong {
  display: block;
  margin-bottom: 6px;
  font-size: 0.9rem;
}

.townsquare__help-panel p {
  margin: 0 0 8px;
  color: var(--ink-soft);
  font-size: 0.84rem;
  line-height: 1.35;
}

.townsquare__help-panel p:last-of-type {
  margin-bottom: 0;
}

.townsquare__help-links {
  margin-bottom: 0;
}

.townsquare__help-panel a {
  color: var(--you-deep);
  font-size: 0.84rem;
  font-weight: 650;
  text-decoration: none;
}

.townsquare__help-panel a:hover,
.townsquare__help-panel a:focus-visible {
  text-decoration: underline;
}

/* STATUS ROW: contains nameplate and other status elements. Positioned at top-left. */
.townsquare__status {
  position: absolute;
  z-index: var(--ts-z-ui);
  top: var(--ts-status-inset);
  left: 4px;
  right: 124px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0;
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1.3;
  pointer-events: none;
}

.townsquare__status[hidden] {
  display: none;
}

.townsquare__stage {
  position: relative;
  height: var(--ts-zone-stage-height);
  border-radius: var(--r) var(--r) 0 0;
  background: transparent;
  /* Horizontal touch swipes walk; vertical gestures still pan the host page. */
  touch-action: pan-y pinch-zoom;
  /* Size speech bubbles/trays/composers against stage width (cqw), not viewport,
     so long lines don't bleed past the clipped stage edge. */
  container-type: inline-size;
}

/* GROUND ZONE: nameplate band between stage and action-zone.
   Fixed height with ground line as top border (marks walkable plane for props).
   Independent zone, painted only with data-townsquare-surface (preview or pasted CSS). */
.townsquare__ground {
  height: var(--ts-zone-ground-height);
  background: transparent;
  border-top: 1px solid transparent;
}

#townsquare-root[data-townsquare-surface] .townsquare__ground {
  background: var(--ground-fill, var(--page));
  border-top: 1px solid var(--ground-line, var(--ground));
}

/* ZONE THEMING: each zone colored when data-townsquare-surface is active.
   Live preview sets this via applySiteStyle; hosted embeds set it at mount.
   Only palette tokens are pasted (buildSiteCss) — all color paint is here.
   Fallbacks (--page, --ground) maintain compatibility with pre-rename CSS. */
#townsquare-root[data-townsquare-surface] .townsquare__stage {
  background: var(--scene);
}

.townsquare .prop {
  position: absolute;
  bottom: var(--ts-ground-level);
  transform: translateX(-50%);
  color: var(--prop-ink);
  opacity: 0.5;
  pointer-events: none;
}

.townsquare .prop svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.townsquare .prop svg * {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* Neighbouring-town fingerposts at the stage edges. */
.townsquare .townsquare-signpost {
  position: absolute;
  bottom: var(--ts-ground-level);
  display: block;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--prop-ink);
  cursor: pointer;
  z-index: 3;
}

.townsquare .townsquare-signpost--left {
  left: 12px;
}

.townsquare .townsquare-signpost--right {
  right: 12px;
}

.townsquare-signpost__sign {
  display: block;
  width: 22px;
  height: 38px;
  opacity: 0.6;
  transition: opacity var(--ts-transition-short) ease, color var(--ts-transition-short) ease;
}

.townsquare-signpost__sign svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.townsquare-signpost__sign svg * {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.3;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.townsquare-signpost:hover .townsquare-signpost__sign,
.townsquare-signpost:focus-visible .townsquare-signpost__sign,
.townsquare-signpost--near .townsquare-signpost__sign {
  color: var(--ink);
  opacity: 1;
}

.townsquare-signpost:focus-visible {
  outline: none;
}

.townsquare-signpost:focus-visible .townsquare-signpost__sign {
  outline: 3px solid color-mix(in oklab, var(--you) 48%, transparent);
  outline-offset: 2px;
  border-radius: 4px;
}

.townsquare-signpost__hint {
  position: absolute;
  bottom: calc(100% + 4px);
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--you);
  color: #fff;
  font-size: 10px;
  line-height: 1.3;
  white-space: nowrap;
  opacity: 0;
  transform: translateY(2px);
  transition: opacity 120ms ease, transform 120ms ease;
  pointer-events: none;
}

.townsquare-signpost--left .townsquare-signpost__hint {
  left: 0;
}

.townsquare-signpost--right .townsquare-signpost__hint {
  right: 0;
}

.townsquare-signpost--near .townsquare-signpost__hint {
  opacity: 1;
  transform: translateY(0);
}

/* Owner message board: a clickable notice prop with an unread "!" badge. */
.townsquare .townsquare-board {
  --board-accent: var(--you);
  position: absolute;
  bottom: var(--ts-ground-level);
  transform: translateX(-50%);
  display: block;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--prop-ink);
  cursor: pointer;
  z-index: 3;
}

.townsquare-board__art {
  display: block;
  width: 100%;
  height: 100%;
  opacity: 0.6;
  transition: opacity var(--ts-transition-short) ease, color var(--ts-transition-short) ease;
}

.townsquare-board__art svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.townsquare-board__art svg * {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.townsquare-board:hover .townsquare-board__art,
.townsquare-board:focus-visible .townsquare-board__art {
  color: var(--ink);
  opacity: 1;
}

.townsquare-board:focus-visible {
  outline: none;
}

.townsquare-board:focus-visible .townsquare-board__art {
  outline: 3px solid color-mix(in oklab, var(--you) 48%, transparent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* Art variants fill the panel differently; the line-art frame is shared. */
.townsquare-board--corkboard .townsquare-board__art .panel {
  fill: color-mix(in oklab, var(--board-accent) 18%, var(--surface));
}

.townsquare-board--chalkboard .townsquare-board__art .panel {
  fill: color-mix(in oklab, var(--ink) 78%, var(--surface));
}

.townsquare-board--chalkboard .townsquare-board__art .note {
  stroke: var(--surface);
}

/* "sign" keeps the default outline-only panel. */

.townsquare-board__badge {
  position: absolute;
  top: -6px;
  right: -6px;
  display: none;
  width: 16px;
  height: 16px;
  border-radius: 999px;
  background: var(--board-accent);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  box-shadow: 0 1px 3px -1px rgba(42, 41, 38, 0.5);
}

.townsquare-board--unread .townsquare-board__badge {
  display: block;
}

/* Message board modal — mirrors the connections overlay shape. */
.townsquare-board-modal {
  --board-accent: var(--you);
  position: absolute;
  inset: 0;
  z-index: 2147483005;
  display: flex;
  align-items: center;
  justify-content: center;
}

.townsquare-board-modal__backdrop {
  position: absolute;
  inset: 0;
  background: color-mix(in oklab, var(--ink) 38%, transparent);
}

.townsquare-board-modal__panel {
  position: relative;
  z-index: 1;
  width: min(320px, 88%);
  max-height: 86%;
  overflow: auto;
  padding: 14px 16px;
  border: 1px solid var(--hair-2);
  border-top: 3px solid var(--board-accent);
  border-radius: 14px;
  background: var(--surface);
  color: var(--ink);
  box-shadow: 0 18px 40px -24px rgba(42, 41, 38, 0.6);
}

.townsquare-board-modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}

.townsquare-board-modal__title {
  font-size: 14px;
  font-weight: 600;
}

.townsquare-board-modal__close {
  border: 0;
  background: transparent;
  color: var(--ink-soft);
  font-size: 20px;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
}

.townsquare-board-modal__close:hover,
.townsquare-board-modal__close:focus-visible {
  color: var(--ink);
}

.townsquare-board-modal__body {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* "Walk over" modal listing the towns on one side. */
.townsquare-connections {
  position: absolute;
  inset: 0;
  z-index: 2147483005;
  display: flex;
  align-items: center;
  justify-content: center;
}

.townsquare-connections__backdrop {
  position: absolute;
  inset: 0;
  background: color-mix(in oklab, var(--ink) 38%, transparent);
}

.townsquare-connections__panel {
  position: relative;
  z-index: 1;
  width: min(320px, 86%);
  max-height: 86%;
  overflow: auto;
  padding: 14px 16px;
  border: 1px solid var(--hair-2);
  border-radius: 14px;
  background: var(--surface);
  color: var(--ink);
  box-shadow: 0 18px 40px -24px rgba(42, 41, 38, 0.6);
}

.townsquare-connections__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}

.townsquare-connections__title {
  font-size: 14px;
  font-weight: 600;
}

.townsquare-connections__close {
  border: 0;
  background: transparent;
  color: var(--ink-soft);
  font-size: 20px;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
}

.townsquare-connections__close:hover,
.townsquare-connections__close:focus-visible {
  color: var(--ink);
}

.townsquare-connections__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.townsquare-connections__item {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas:
    "name go"
    "host go";
  align-items: center;
  column-gap: 10px;
  padding: 9px 11px;
  border: 1px solid color-mix(in oklab, var(--hair-2) 70%, transparent);
  border-radius: 8px;
  text-decoration: none;
  color: inherit;
  transition: border-color 120ms ease, background 120ms ease;
}

.townsquare-connections__item:hover,
.townsquare-connections__item:focus-visible {
  border-color: var(--you);
  background: var(--you-tint);
  outline: none;
}

.townsquare-connections__item-name {
  grid-area: name;
  font-size: 13px;
}

.townsquare-connections__item-host {
  grid-area: host;
  font-size: 11px;
  color: var(--ink-soft);
}

.townsquare-connections__item-go {
  grid-area: go;
  font-size: 12px;
  color: var(--you-deep);
  white-space: nowrap;
}

.townsquare--quiet {
  min-height: 46px;
  overflow: visible;
}

.townsquare--quiet .townsquare__status,
.townsquare--quiet .townsquare__actions,
.townsquare--quiet .townsquare__toolbar,
.townsquare--quiet .townsquare__stage {
  display: none;
}

.townsquare--quiet .townsquare__button--expand {
  display: none;
}

.townsquare--expanded {
  position: fixed;
  inset: 0;
  z-index: 2147483000;
  width: 100vw;
  height: 100dvh;
  /* Reserve room at the bottom for the docked toolbar (absolute, bottom:7px) so
     the flex stage — and the figures' name tags at its base — never sit under
     the chat input. Mirrors --ts-toolbar-reserve in non-expanded mode. No
     horizontal padding: it would expose the container background as a colored
     strip down each side of the full-bleed stage. */
  padding: 0 0 var(--ts-toolbar-reserve);
  border-top: 0;
  border-radius: 0;
  /* Opaque backdrop so the fullscreen overlay always covers the host page. The
     flat stage paints --scene over the whole scene, so this only shows through
     where --scene is transparent (then reading as neutral Canvas, never the
     ground color) or behind the docked toolbar. */
  background: Canvas;
  display: flex;
  flex-direction: column;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

.townsquare--expanded::after {
  content: "";
  display: block;
  flex: 0 0 var(--ts-keyboard-scroll-room, 0px);
}

/* EXPANDED MODE: stage flex-grows, controls/status repositioned with larger insets. */
.townsquare--expanded {
  --ts-controls-inset: 10px;
  --ts-status-inset: 10px;
}

.townsquare--expanded .townsquare__controls {
  right: 18px;
}

.townsquare--expanded .townsquare__status {
  left: 18px;
  right: 148px;
}

.townsquare--expanded .townsquare__stage {
  flex: 1 1 auto;
  min-height: 0;
}

.townsquare .prop--tree .canopy {
  fill: var(--tree-canopy);
  stroke: var(--tree-canopy);
}

.townsquare .prop--tree .trunk {
  fill: var(--tree-trunk);
  stroke: var(--tree-trunk);
}

.townsquare .prop--lamp {
  z-index: 1;
}

.townsquare .prop__light {
  position: absolute;
  top: 16%;
  left: 75%;
  width: 90px;
  height: 88%;
  transform: translateX(-50%);
  clip-path: polygon(38% 0%, 62% 0%, 100% 100%, 0% 100%);
  background: linear-gradient(
    to bottom,
    rgba(253, 230, 160, 0.5) 0%,
    rgba(253, 230, 160, 0.2) 42%,
    rgba(253, 230, 160, 0.04) 100%
  );
  opacity: 0.34;
}

.townsquare__birds {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
}

/* Ambient drifting clouds — only painted in expanded mode, where the taller
   sky gives them room. Kept display:none while collapsed so the drift keyframes
   don't tick in the background. */
.townsquare__clouds {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 0;
  display: none;
}

.townsquare--expanded .townsquare__clouds {
  display: block;
}

.cloud {
  position: absolute;
  top: var(--cloud-top, 12%);
  left: 0;
  width: calc(64px * var(--cloud-scale, 1));
  color: rgba(255, 255, 255, 0.82);
  filter: drop-shadow(0 2px 6px rgba(70, 90, 120, 0.12));
  will-change: transform;
  animation: cloud-drift var(--cloud-duration, 60s) linear infinite;
  animation-delay: var(--cloud-delay, 0s);
}

.cloud svg {
  display: block;
  width: 100%;
  height: auto;
}

.cloud svg path {
  fill: currentColor;
}

@keyframes cloud-drift {
  from { transform: translateX(-140px); }
  to { transform: translateX(calc(100cqw + 40px)); }
}

/* Freeze the drift but keep the clouds in place — the negative start delays
   leave them spread across the sky rather than parked off-screen. */
@media (prefers-reduced-motion: reduce) {
  .cloud { animation-play-state: paused; }
}

/* ============================================================================
   WEATHER — ambient light rain, thunderstorm, and snow

   Painted by widget/weather.mjs into .townsquare__weather (a stage overlay).
   Base precipitation, the sky tint, and storm flashes show in the collapsed
   widget too, so a passing storm is legible at a glance. High-altitude detail
   — weather clouds, lightning bolts, background depth precipitation — carries
   .weather--high and only paints in expanded mode, where the taller sky gives
   it room. All motion is CSS keyframes on transform/opacity; JS never
   animates weather.
   ============================================================================ */

.townsquare__weather {
  /* How far a particle falls before recycling: a little past the collapsed
     stage height. Expanded stretches it to the viewport-tall sky, with a time
     stretch so the fall reads slightly faster, not 4× faster. */
  --ts-weather-fall: 230px;
  --ts-weather-time-scale: 1;
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  /* Above props and figures — precipitation is the nearest scene layer — but
     below the widget UI and chat bubbles/trays (--ts-z-ui and up). */
  z-index: 1000;
}

.townsquare--expanded .townsquare__weather {
  --ts-weather-fall: 105dvh;
  --ts-weather-time-scale: 2.4;
}

/* Sky tint — the strongest collapsed-view tell that the weather changed. */
.townsquare__weather::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--ts-weather-tint, transparent);
  opacity: 0;
  transition: opacity 800ms ease;
}

.townsquare--weather-rain .townsquare__weather::before {
  --ts-weather-tint: rgba(96, 112, 132, 0.14);
  opacity: 1;
}

.townsquare--weather-storm .townsquare__weather::before {
  --ts-weather-tint: rgba(42, 50, 68, 0.26);
  opacity: 1;
}

.townsquare--weather-snow .townsquare__weather::before {
  --ts-weather-tint: rgba(226, 234, 244, 0.16);
  opacity: 1;
}

/* High-altitude detail only exists in the expanded sky. */
.townsquare__weather .weather--high {
  display: none;
}

.townsquare--expanded .townsquare__weather .weather--high {
  display: block;
}

.weather-drop {
  position: absolute;
  top: -18px;
  left: var(--wx, 50%);
  width: 1px;
  height: calc(4px * var(--w-scale, 1));
  border-radius: 1px;
  background: color-mix(in oklab, var(--ink) 42%, transparent);
  animation: weather-fall calc(var(--w-duration, 1.1s) * var(--ts-weather-time-scale, 1)) linear infinite;
  animation-delay: var(--w-delay, 0s);
}

.weather-drop.weather--high {
  width: 1.5px;
  opacity: 0.5;
}

/* Storm rain: thicker, darker, and slanted. The independent `rotate` composes
   with the animated transform, so the fall vector tilts with the streak and
   the drops travel along their own slant. */
.townsquare--weather-storm .weather-drop {
  width: 1.5px;
  height: calc(4px * var(--w-scale, 1));
  rotate: -14deg;
  background: color-mix(in oklab, var(--ink) 52%, transparent);
}

.townsquare--weather-storm .weather-drop.weather--high {
  width: 1.5px;
}

.weather-flake {
  position: absolute;
  top: -12px;
  left: var(--wx, 50%);
  animation: weather-fall calc(var(--w-duration, 6s) * var(--ts-weather-time-scale, 1)) linear infinite;
  animation-delay: var(--w-delay, 0s);
}

.weather-flake i {
  display: block;
  width: calc(5px * var(--w-scale, 1));
  height: calc(5px * var(--w-scale, 1));
  border-radius: 50%;
  /* A whisper of ink keeps flakes legible on pale skies in both themes. */
  background: color-mix(in oklab, #fff 86%, var(--ink));
  box-shadow: 0 1px 2px color-mix(in oklab, var(--ink) 18%, transparent);
  animation: weather-sway var(--w-sway-duration, 2.2s) ease-in-out infinite alternate;
  animation-delay: var(--w-delay, 0s);
}

.weather-flake.weather--high {
  opacity: 0.55;
}

/* Settled snow along the ground line — visible collapsed too. */
.weather-ground {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  height: 7px;
  background: linear-gradient(to top, rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0));
}

/* Lightning: a brief double-pulse sheet flash over the whole sky (both views),
   plus expanded-only bolts that share the same cycle so they strike together. */
.weather-flash {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to bottom,
    rgba(235, 242, 255, 0.85),
    rgba(235, 242, 255, 0.28) 60%,
    rgba(235, 242, 255, 0) 100%
  );
  opacity: 0;
  animation: weather-flash var(--flash-duration, 9s) linear infinite;
  animation-delay: var(--flash-delay, 0s);
}

.weather-bolt {
  position: absolute;
  top: var(--bolt-top, 6%);
  left: var(--bolt-x, 30%);
  width: clamp(26px, 5cqw, 54px);
  color: #f4f8ff;
  filter: drop-shadow(0 0 6px rgba(190, 214, 255, 0.9));
  opacity: 0;
  animation: weather-bolt var(--flash-duration, 9s) linear infinite;
  animation-delay: var(--flash-delay, 0s);
}

.weather-bolt svg {
  display: block;
  width: 100%;
  height: auto;
}

.weather-bolt svg path {
  fill: currentColor;
}

/* Weather clouds ride the ambient .cloud drift, recolored per kind; the
   ambient cloud layer itself also darkens so the whole sky agrees. */
.weather-cloud {
  color: rgba(148, 163, 184, 0.78);
}

.townsquare--weather-storm .weather-cloud {
  color: rgba(84, 96, 116, 0.88);
}

.townsquare--weather-rain .townsquare__clouds .cloud {
  color: rgba(170, 182, 196, 0.8);
}

.townsquare--weather-storm .townsquare__clouds .cloud {
  color: rgba(112, 124, 142, 0.8);
}

@keyframes weather-fall {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(0, var(--ts-weather-fall, 230px), 0);
  }
}

@keyframes weather-sway {
  from {
    transform: translateX(calc(-1 * var(--w-sway, 7px)));
  }
  to {
    transform: translateX(var(--w-sway, 7px));
  }
}

@keyframes weather-flash {
  0%, 4.4% { opacity: 0; }
  5% { opacity: 0.85; }
  5.6% { opacity: 0.12; }
  6.2% { opacity: 0.55; }
  8% { opacity: 0; }
  100% { opacity: 0; }
}

@keyframes weather-bolt {
  0%, 4.4% { opacity: 0; }
  4.8%, 6% { opacity: 1; }
  7% { opacity: 0; }
  100% { opacity: 0; }
}

/* Freeze the motion but keep the sky tint and the scattered particles (their
   negative delays leave them mid-fall), so the weather still reads as a
   static illustration. */
@media (prefers-reduced-motion: reduce) {
  .weather-drop,
  .weather-flake,
  .weather-flake i,
  .weather-flash,
  .weather-bolt {
    animation-play-state: paused;
  }
}

.bird {
  position: absolute;
  left: calc(var(--bird-x) * 100%);
  bottom: calc(var(--ts-ground-level) + var(--bird-lift, 0px));
  width: 12px;
  height: 8px;
  transform: translateX(-50%);
  color: var(--prop-ink);
  opacity: 0.45;
  pointer-events: none;
}

.bird svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.bird svg * {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.bird--arriving svg,
.bird--fleeing svg {
  transform: scaleX(var(--bird-facing, 1));
  transform-origin: 50% 50%;
}

.bird--arriving .bird-wing,
.bird--fleeing .bird-wing {
  transform-box: fill-box;
  animation-duration: 0.16s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}

.bird--arriving .bird-wing--left,
.bird--fleeing .bird-wing--left {
  transform-origin: 100% 0%;
  animation-name: bird-wing-left;
}

.bird--arriving .bird-wing--right,
.bird--fleeing .bird-wing--right {
  transform-origin: 0% 0%;
  animation-name: bird-wing-right;
}

.bird--arriving {
  animation: bird-arrive 1.2s ease-out forwards;
}

.bird--perched {
  width: 16px;
  height: 11px;
  animation: bird-bob 3s ease-in-out infinite;
}

.bird--perched svg {
  transform: scaleX(var(--bird-facing, 1));
  transform-origin: 50% 100%;
}

.bird--fleeing {
  animation: bird-flee 0.7s ease-in forwards;
}

@keyframes bird-wing-left {
  from {
    transform: rotate(16deg);
  }

  to {
    transform: rotate(-22deg);
  }
}

@keyframes bird-wing-right {
  from {
    transform: rotate(-16deg);
  }

  to {
    transform: rotate(22deg);
  }
}

@keyframes bird-arrive {
  from {
    left: calc(var(--bird-from) * 100%);
    bottom: calc(var(--ts-ground-level) + var(--bird-lift, 0px) + 24px);
    opacity: 0;
  }

  25% {
    bottom: calc(var(--ts-ground-level) + var(--bird-lift, 0px) + 20px);
  }

  50% {
    bottom: calc(var(--ts-ground-level) + var(--bird-lift, 0px) + 26px);
  }

  75% {
    bottom: calc(var(--ts-ground-level) + var(--bird-lift, 0px) + 18px);
  }

  to {
    left: calc(var(--bird-to) * 100%);
    bottom: calc(var(--ts-ground-level) + var(--bird-lift, 0px));
    opacity: 0.45;
  }
}

@keyframes bird-bob {
  0%,
  100% {
    transform: translateX(-50%) translateY(0);
  }

  50% {
    transform: translateX(-50%) translateY(-2px);
  }
}

@keyframes bird-flee {
  from {
    left: calc(var(--bird-x) * 100%);
    bottom: calc(var(--ts-ground-level) + var(--bird-lift, 0px));
    opacity: 0.45;
  }

  35% {
    bottom: calc(var(--ts-ground-level) + var(--bird-lift, 0px) + 28px);
  }

  to {
    left: calc((var(--bird-x) + var(--bird-dir, 1) * 0.18) * 100%);
    bottom: calc(var(--ts-ground-level) + var(--bird-lift, 0px) + 42px);
    opacity: 0;
  }
}

.townsquare-avatar {
  --walk-cycle: 0.8s;
  --townsquare-avatar-walk-filter: ;
  --townsquare-avatar-shade-filter: ;
  --townsquare-avatar-light-filter: ;
  /* Horizontal position rides the compositor: --avatar-x (0–100, set per frame
     by renderAvatar) is a percentage of the stage width via cqw, and the
     trailing translateX(-50%) centres the figure on it. Keeping position off
     `left` means walking never dirties layout. Jump/high-five keyframes reuse
     --avatar-pos so their hop composes on top of the live position. */
  --avatar-pos: translateX(calc(var(--avatar-x, 50) * 1cqw)) translateX(-50%);
  position: absolute;
  bottom: var(--ts-ground-level);
  left: 0;
  width: 18px;
  height: 46px;
  transform: var(--avatar-pos);
  transition: transform var(--ts-transition-micro) linear;
  /* --speak-order: most recent speaker stacks on top when columns must overlap */
  z-index: var(--speak-order, 0);
}

.townsquare-avatar.townsquare-avatar--flipped svg {
  transform: scaleX(-1);
  transform-origin: 50% 50%;
}

.townsquare-avatar svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.townsquare-avatar svg * {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.townsquare-avatar svg circle.head {
  fill: currentColor;
}

.townsquare-avatar--self {
  color: var(--you);
  z-index: 2147483001;
}

.townsquare-avatar--profile-open {
  z-index: 2147483004;
}

.townsquare-avatar--peer { color: var(--other); }

/* peers are clickable (recent-message tray); the cursor also marks the slim
   figure as interactive, and on iOS Safari makes taps produce click events */
.townsquare-avatar--peer { cursor: pointer; }

/* widen the 18px figure's click/tap zone so "click a person" doesn't need
   precision — clicks here open their tray instead of walking */
.townsquare-avatar--peer::before {
  content: "";
  position: absolute;
  inset: -8px -14px;
}

/* a character's lingering bubbles are part of "them": clicking one opens
   their tray (the container keeps pointer-events: none for walk taps) */
.townsquare-avatar--peer .townsquare-avatar__bubble {
  pointer-events: auto;
  cursor: pointer;
}

/* while the tray is open the faded-out bubbles must not block clicks on it */
.townsquare-avatar--tray-open .townsquare-avatar__bubble {
  pointer-events: none;
}

/* Reading a character's tray outranks everything in the scene — neighbours'
   speak-order stacking AND the self figure (2147483001), so the open tray is
   never painted over by your own figure/bubbles. Stays below the overlay panels
   (message board / connections, 2147483005) and the help modal. */
.townsquare-avatar--has-history:hover,
.townsquare-avatar--has-history:focus-within,
.townsquare-avatar--tray-open { z-index: 2147483003; }
.townsquare-avatar:is(.townsquare-avatar--walking, .townsquare-avatar--shaded, .townsquare-avatar--lit) {
  filter: var(--townsquare-avatar-shade-filter) var(--townsquare-avatar-walk-filter) var(--townsquare-avatar-light-filter);
}
.townsquare-avatar.townsquare-avatar--walking { --townsquare-avatar-walk-filter: drop-shadow(0 2px 5px rgba(0,0,0,0.12)); }
.townsquare-avatar--jumping {
  animation: presence-jump var(--ts-transition-long) cubic-bezier(0.2, 0.8, 0.2, 1);
}
.townsquare-avatar--high-five {
  animation: presence-high-five-root 760ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.townsquare-avatar--reading-away > svg,
.townsquare-avatar--reading-away .townsquare-avatar__below {
  opacity: 0.22;
}

/* Long-idle figures fade away entirely; returning to the tab brings them back. */
.townsquare-avatar--asleep {
  opacity: 0;
  pointer-events: none;
  transition: opacity 600ms ease;
}

.townsquare-avatar--reading-away::after {
  content: "Zz";
  position: absolute;
  left: 50%;
  top: -12px;
  transform: translateX(-50%) rotate(-8deg);
  padding: 1px 4px;
  border-radius: 999px;
  background: color-mix(in oklab, var(--surface) 70%, transparent);
  color: color-mix(in oklab, currentColor 58%, transparent);
  font-size: 10px;
  font-weight: 720;
  line-height: 1;
  letter-spacing: 0;
  pointer-events: none;
}

.townsquare-avatar--typing:not(.townsquare-avatar--raised-hand)::after {
  content: "";
  position: absolute;
  left: 50%;
  top: -16px;
  width: 26px;
  height: 15px;
  padding: 0;
  transform: translateX(-50%);
  border-radius: 999px;
  background-color: color-mix(in oklab, var(--surface) 88%, transparent);
  background-image:
    radial-gradient(circle, currentColor 2px, transparent 2.5px),
    radial-gradient(circle, currentColor 2px, transparent 2.5px),
    radial-gradient(circle, currentColor 2px, transparent 2.5px);
  background-position: 5px 6px, 11px 8px, 17px 8px;
  background-repeat: no-repeat;
  background-size: 5px 5px;
  color: color-mix(in oklab, currentColor 62%, transparent);
  box-shadow: 0 1px 3px rgba(0,0,0,0.12);
  animation: presence-typing 900ms ease-in-out infinite;
  pointer-events: none;
}

@keyframes presence-typing {
  0%, 80%, 100% { background-position: 5px 8px, 11px 8px, 17px 8px; }
  20% { background-position: 5px 4px, 11px 8px, 17px 8px; }
  40% { background-position: 5px 8px, 11px 4px, 17px 8px; }
  60% { background-position: 5px 8px, 11px 8px, 17px 4px; }
}

@media (prefers-reduced-motion: reduce) {
  .townsquare-avatar--typing::after { animation: none; }
}

.townsquare-avatar--raised-hand::after {
  content: "🙌";
  position: absolute;
  left: 50%;
  top: -18px;
  transform: translateX(-50%);
  font-size: 13px;
  line-height: 1;
  filter: drop-shadow(0 1px 2px rgba(0,0,0,0.18));
  pointer-events: none;
}

/* Pose anchors: each pose sinks the element box below the plane by exactly the
   distance its lowest rotated foot ends above the box bottom, so the feet stay
   planted on the ground line while the pose bends the legs. Derived from the
   figure geometry (figure.mjs, 0.9 px/unit; feet at y=42 = box bottom) and the
   pose rotations below:
   - sitting (bench): hip 70–74°, knee −80/−84° → lowest foot at y≈38.3
     → drop (42 − 38.3) × 0.9 ≈ 3.3px. The hip (torso end, y=26 → 14.4px above
     the box bottom) then lands 14.4 − 3.3 ≈ 11.1px above the plane, resting on
     the 10px bench seat plank (--ts-bench-seat-height + half strokes).
   - resting (under a tree): hip 118/120°, knee −85/−88°, core −10° → lowest
     foot at y≈33.8 → drop (42 − 33.8) × 0.9 ≈ 7.4px. */
.townsquare-avatar--sitting { bottom: calc(var(--ts-ground-level) - 3.3px); }
.townsquare-avatar--resting { bottom: calc(var(--ts-ground-level) - 7.4px); }
.townsquare-avatar--shaded { --townsquare-avatar-shade-filter: brightness(0.88); }
.townsquare-avatar--lit {
  --townsquare-avatar-light-filter: drop-shadow(0 0 3px rgba(253, 230, 160, 0.7)) drop-shadow(0 0 6px rgba(253, 230, 160, 0.25));
}

/* Walk-sandbox preview: fills the preview box instead of standing on a stage. */
.townsquare-avatar--preview {
  position: static;
  width: 100%;
  height: 100%;
  transform: none;
  transition: none;
}

.townsquare-avatar--preview.townsquare-avatar--walking { filter: none; }
.townsquare-avatar--preview svg * { stroke-width: 1.4; }

.townsquare-avatar svg :is(g.figure-core, g.joint, g.book, g.umbrella, g.umbrella-canopy) {
  transform-box: view-box;
}

.townsquare-avatar svg g.figure-core { transform-origin: 10px 24px; }
.townsquare-avatar svg g.arm-l { transform-origin: 9.4px 14px; }
.townsquare-avatar svg g.arm-r { transform-origin: 10.6px 14px; }
.townsquare-avatar svg g.elbow-l { transform-origin: 6.1px 20px; }
.townsquare-avatar svg g.elbow-r { transform-origin: 13.9px 20px; }
.townsquare-avatar svg g.leg-l { transform-origin: 9.2px 26px; }
.townsquare-avatar svg g.leg-r { transform-origin: 10.8px 26px; }
.townsquare-avatar svg g.knee-l { transform-origin: 7.1px 34px; }
.townsquare-avatar svg g.knee-r { transform-origin: 12.9px 34px; }
.townsquare-avatar svg g.book { transform-origin: 12.15px 18.7px; }
/* Umbrella pivots: the whole umbrella about the grip point on the shaft, the
   canopy about the point where it meets the shaft, so the pop-open unfurls
   outward and upward from the shaft rather than from the figure's centre. */
.townsquare-avatar svg g.umbrella { transform-origin: 8.5px 21px; }
.townsquare-avatar svg g.umbrella-canopy { transform-origin: 8.5px 0.2px; }

.townsquare-avatar.townsquare-avatar--walking svg :is(g.figure-core, g.joint) {
  animation-duration: var(--walk-cycle);
  /* linear between frames: the easing lives in the keyframe spacing itself,
     so motion doesn't decelerate/re-accelerate at every keyframe. */
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

.townsquare-avatar.townsquare-avatar--walking svg g.figure-core { animation-name: presence-body-bob; }
.townsquare-avatar.townsquare-avatar--walking svg g.leg-l { animation-name: presence-leg-a; }
.townsquare-avatar.townsquare-avatar--walking svg g.leg-r { animation-name: presence-leg-b; }
.townsquare-avatar.townsquare-avatar--walking svg g.knee-l { animation-name: presence-knee-a; }
.townsquare-avatar.townsquare-avatar--walking svg g.knee-r { animation-name: presence-knee-b; }
.townsquare-avatar.townsquare-avatar--walking svg g.arm-l { animation-name: presence-arm-a; }
.townsquare-avatar.townsquare-avatar--walking svg g.arm-r { animation-name: presence-arm-b; }
.townsquare-avatar.townsquare-avatar--walking svg g.elbow-l { animation-name: presence-forearm-a; }
.townsquare-avatar.townsquare-avatar--walking svg g.elbow-r { animation-name: presence-forearm-b; }

.townsquare-avatar--jumping svg :is(g.figure-core, g.joint) {
  animation-duration: 560ms;
  animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
  animation-iteration-count: 1;
}

.townsquare-avatar--jumping svg g.figure-core { animation-name: presence-jump-body; }
.townsquare-avatar--jumping svg g.leg-l { animation-name: presence-jump-leg-a; }
.townsquare-avatar--jumping svg g.leg-r { animation-name: presence-jump-leg-b; }
.townsquare-avatar--jumping svg g.knee-l { animation-name: presence-jump-knee-a; }
.townsquare-avatar--jumping svg g.knee-r { animation-name: presence-jump-knee-b; }
.townsquare-avatar--jumping svg g.arm-l { animation-name: presence-jump-arm-a; }
.townsquare-avatar--jumping svg g.arm-r { animation-name: presence-jump-arm-b; }
.townsquare-avatar--jumping svg g.elbow-l { animation-name: presence-jump-forearm-a; }
.townsquare-avatar--jumping svg g.elbow-r { animation-name: presence-jump-forearm-b; }

.townsquare-avatar.townsquare-avatar--high-five svg :is(g.figure-core, g.joint) {
  animation-duration: 760ms;
  animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
  animation-iteration-count: 1;
}

.townsquare-avatar.townsquare-avatar--high-five svg g.figure-core { animation-name: presence-high-five-body; }
.townsquare-avatar.townsquare-avatar--high-five svg g.arm-l { animation-name: presence-high-five-arm-idle; }
.townsquare-avatar.townsquare-avatar--high-five svg g.elbow-l { animation-name: presence-high-five-forearm-idle; }
.townsquare-avatar.townsquare-avatar--high-five svg g.arm-r { animation-name: presence-high-five-arm; }
.townsquare-avatar.townsquare-avatar--high-five svg g.elbow-r { animation-name: presence-high-five-forearm; }

/*
  Walk cycle, figure facing right (the flipped state mirrors it for left —
  the pose data itself never changes with direction).

  Sign conventions in the un-mirrored pose:
    legs/arms  positive deg = limb swung backward, negative = forward
    knees      positive deg = flexion (heel toward butt); never negative
    elbows     negative deg = flexion (hand swings forward); never positive

  Phase layout: left-leg heel strike at 0%, right-leg heel strike at 50%.
  Each leg spends ~60% of the cycle in stance and ~40% in swing, with peak
  knee flexion mid-swing for ground clearance.
*/
@keyframes presence-body-bob {
  0%, 50%, 100% { transform: translateY(0.4px) rotate(0.6deg); }
  6%, 56% { transform: translateY(0.7px) rotate(0.9deg); }
  28%, 78% { transform: translateY(-0.6px) rotate(1.2deg); }
}

@keyframes presence-leg-a {
  0%, 100% { transform: rotate(-20deg); }
  8% { transform: rotate(-18deg); }
  30% { transform: rotate(-2deg); }
  45% { transform: rotate(10deg); }
  55% { transform: rotate(14deg); }
  62% { transform: rotate(15deg); }
  75% { transform: rotate(-4deg); }
  88% { transform: rotate(-17deg); }
  94% { transform: rotate(-21deg); }
}

@keyframes presence-leg-b {
  0%, 100% { transform: rotate(12deg); }
  5% { transform: rotate(14deg); }
  12% { transform: rotate(15deg); }
  25% { transform: rotate(-4deg); }
  38% { transform: rotate(-17deg); }
  44% { transform: rotate(-21deg); }
  50% { transform: rotate(-20deg); }
  58% { transform: rotate(-18deg); }
  80% { transform: rotate(-2deg); }
  95% { transform: rotate(10deg); }
}

@keyframes presence-knee-a {
  0%, 100% { transform: rotate(4deg); }
  8% { transform: rotate(12deg); }
  20% { transform: rotate(6deg); }
  40% { transform: rotate(5deg); }
  55% { transform: rotate(18deg); }
  65% { transform: rotate(32deg); }
  72% { transform: rotate(38deg); }
  80% { transform: rotate(28deg); }
  90% { transform: rotate(8deg); }
  96% { transform: rotate(3deg); }
}

@keyframes presence-knee-b {
  0%, 100% { transform: rotate(14deg); }
  5% { transform: rotate(18deg); }
  15% { transform: rotate(32deg); }
  22% { transform: rotate(38deg); }
  30% { transform: rotate(28deg); }
  40% { transform: rotate(8deg); }
  46% { transform: rotate(3deg); }
  50% { transform: rotate(4deg); }
  58% { transform: rotate(12deg); }
  70% { transform: rotate(6deg); }
  90% { transform: rotate(5deg); }
}

@keyframes presence-arm-a {
  0%, 100% { transform: rotate(12deg); }
  12% { transform: rotate(10deg); }
  30% { transform: rotate(1deg); }
  50% { transform: rotate(-10deg); }
  60% { transform: rotate(-11deg); }
  75% { transform: rotate(-5deg); }
  90% { transform: rotate(7deg); }
}

@keyframes presence-arm-b {
  0%, 100% { transform: rotate(-10deg); }
  10% { transform: rotate(-11deg); }
  25% { transform: rotate(-5deg); }
  40% { transform: rotate(7deg); }
  50% { transform: rotate(12deg); }
  62% { transform: rotate(10deg); }
  80% { transform: rotate(1deg); }
}

@keyframes presence-forearm-a {
  0%, 100% { transform: rotate(-6deg); }
  15% { transform: rotate(-7deg); }
  35% { transform: rotate(-10deg); }
  55% { transform: rotate(-14deg); }
  68% { transform: rotate(-15deg); }
  85% { transform: rotate(-9deg); }
}

@keyframes presence-forearm-b {
  0%, 100% { transform: rotate(-13deg); }
  5% { transform: rotate(-14deg); }
  18% { transform: rotate(-15deg); }
  35% { transform: rotate(-9deg); }
  50% { transform: rotate(-6deg); }
  65% { transform: rotate(-7deg); }
  85% { transform: rotate(-10deg); }
}

@keyframes presence-jump {
  0%, 100% { transform: var(--avatar-pos) translateY(0); }
  12% { transform: var(--avatar-pos) translateY(1px) scaleY(0.96); }
  45% { transform: var(--avatar-pos) translateY(-30px) scaleY(1.03); }
  78% { transform: var(--avatar-pos) translateY(-6px); }
}

@keyframes presence-jump-body {
  0%, 100% { transform: translateY(0) rotate(0); }
  12% { transform: translateY(1.8px) rotate(-2deg); }
  36%, 62% { transform: translateY(-0.8px) rotate(2deg); }
  82% { transform: translateY(1px) rotate(-1deg); }
}

@keyframes presence-jump-leg-a {
  0%, 100% { transform: rotate(-4deg); }
  12% { transform: rotate(20deg); }
  38%, 62% { transform: rotate(-28deg); }
  82% { transform: rotate(12deg); }
}

@keyframes presence-jump-leg-b {
  0%, 100% { transform: rotate(4deg); }
  12% { transform: rotate(24deg); }
  38%, 62% { transform: rotate(24deg); }
  82% { transform: rotate(14deg); }
}

@keyframes presence-jump-knee-a {
  0%, 100% { transform: rotate(6deg); }
  12% { transform: rotate(34deg); }
  38%, 62% { transform: rotate(48deg); }
  82% { transform: rotate(18deg); }
}

@keyframes presence-jump-knee-b {
  0%, 100% { transform: rotate(6deg); }
  12% { transform: rotate(38deg); }
  38%, 62% { transform: rotate(42deg); }
  82% { transform: rotate(18deg); }
}

@keyframes presence-jump-arm-a {
  0%, 100% { transform: rotate(6deg); }
  12% { transform: rotate(24deg); }
  38%, 62% { transform: rotate(118deg); }
  82% { transform: rotate(28deg); }
}

@keyframes presence-jump-arm-b {
  0%, 100% { transform: rotate(-6deg); }
  12% { transform: rotate(-24deg); }
  38%, 62% { transform: rotate(-118deg); }
  82% { transform: rotate(-28deg); }
}

@keyframes presence-jump-forearm-a {
  0%, 100% { transform: rotate(-8deg); }
  12% { transform: rotate(-18deg); }
  38%, 62% { transform: rotate(10deg); }
  82% { transform: rotate(-14deg); }
}

@keyframes presence-jump-forearm-b {
  0%, 100% { transform: rotate(-8deg); }
  12% { transform: rotate(-18deg); }
  38%, 62% { transform: rotate(10deg); }
  82% { transform: rotate(-14deg); }
}

@keyframes presence-high-five-root {
  0%, 100% { transform: var(--avatar-pos) translateY(0); }
  42% { transform: var(--avatar-pos) translateY(-2px); }
  56% { transform: var(--avatar-pos) translateY(1px); }
}

@keyframes presence-high-five-body {
  0%, 100% { transform: translateY(0) rotate(1deg); }
  28%, 62% { transform: translateY(-0.8px) rotate(5deg); }
}

@keyframes presence-high-five-arm {
  0%, 100% { transform: rotate(-132deg); }
  30% { transform: rotate(-112deg); }
  46%, 58% { transform: rotate(-82deg); }
  72% { transform: rotate(-118deg); }
}

@keyframes presence-high-five-forearm {
  0%, 100% { transform: rotate(16deg); }
  30% { transform: rotate(6deg); }
  46%, 58% { transform: rotate(-8deg); }
  72% { transform: rotate(10deg); }
}

@keyframes presence-high-five-arm-idle {
  0%, 100% { transform: rotate(8deg); }
  42%, 62% { transform: rotate(16deg); }
}

@keyframes presence-high-five-forearm-idle {
  0%, 100% { transform: rotate(-8deg); }
  42%, 62% { transform: rotate(-14deg); }
}

.townsquare-avatar--sitting svg :is(g.figure-core, g.joint),
.townsquare-avatar--resting svg :is(g.figure-core, g.joint),
.townsquare-avatar--book svg g.joint {
  animation: none !important;
  transition: transform var(--ts-transition-standard) ease;
}

.townsquare-avatar--sitting svg g.figure-core { transform: translateY(0) rotate(0); }
.townsquare-avatar--sitting svg g.leg-l { transform: rotate(74deg); }
.townsquare-avatar--sitting svg g.leg-r { transform: rotate(70deg); }
.townsquare-avatar--sitting svg g.knee-l { transform: rotate(-84deg); }
.townsquare-avatar--sitting svg g.knee-r { transform: rotate(-80deg); }
.townsquare-avatar--sitting svg g.arm-l,
.townsquare-avatar--sitting svg g.arm-r { transform: rotate(16deg); }
.townsquare-avatar--sitting svg g.elbow-l { transform: rotate(24deg); }
.townsquare-avatar--sitting svg g.elbow-r { transform: rotate(-24deg); }

.townsquare-avatar--resting svg g.figure-core { transform: translateY(0) rotate(-10deg); }
.townsquare-avatar--resting svg g.leg-l { transform: rotate(120deg); }
.townsquare-avatar--resting svg g.leg-r { transform: rotate(118deg); }
.townsquare-avatar--resting svg g.knee-l { transform: rotate(-88deg); }
.townsquare-avatar--resting svg g.knee-r { transform: rotate(-85deg); }
.townsquare-avatar--resting svg g.arm-l,
.townsquare-avatar--resting svg g.arm-r { transform: rotate(45deg); }
.townsquare-avatar--resting svg g.elbow-l { transform: rotate(10deg); }
.townsquare-avatar--resting svg g.elbow-r { transform: rotate(-30deg); }

/* book: held low, around belly/hip height, not raised to shoulder height —
   a book held up near the chin reads as a raised-arm gesture, not reading.
   The book (g.book, figure.mjs) is drawn as two open page-panels sharing a
   spine edge (near panel 9,17.6–22.2; spine at 12.2; far panel 15.3,14.8–
   21.8, taller and offset up) rather than a single flat box, so it reads as
   an open book rather than a closed slab — and it's drawn oversized on
   purpose so it stays legible at this figure's small on-scene scale instead
   of disappearing into the arm strokes. The left arm does the visible work,
   swinging down and folding back to bring the hand to the near panel's
   bottom-outer corner (9,22.2); the right arm stays short, reaching only to
   the near panel's top-outer corner (9,17.6) since it's already close to
   that shoulder. Hand-tuned against a reference sketch rather than solved
   by IK (unlike the sitting/resting poses above), picking whichever of the
   two IK branches per arm needed the smaller shoulder swing, same
   branch-selection rule as sitting/resting; elbow-l's angle was then
   corrected against the actual rendered transform (getScreenCTM on the
   live figure, not hand math) so the hand really lands on the near panel's
   corner instead of stopping short of it. Legs are untouched, so this
   composes with any leg pose. Placed after the sitting/resting rules
   (identical selector specificity) so book-pose's arm angles win on source
   order when a leg pose is also present. */
.townsquare-avatar--book svg g.arm-l { transform: rotate(17deg); }
.townsquare-avatar--book svg g.arm-r { transform: rotate(-12deg); }
.townsquare-avatar--book svg g.elbow-l { transform: rotate(-83deg); }
.townsquare-avatar--book svg g.elbow-r { transform: rotate(114deg); }

/* The right forearm's reach (elbow-r, above) passes behind the book on its
   way to the near panel's top corner, so it — and the lower part of the
   upper arm past where the book would occlude it — is hidden rather than
   drawn crossing on top of the pages. `line.book-stub` (figure.mjs) is a
   shorter line sharing g.arm-r's rotation, showing only the bit of upper arm
   that stays visible in front of the book; `line.limb` is the normal
   full-length upper arm used by every other pose. */
.townsquare-avatar svg g.arm-r line.book-stub { opacity: 0; }
.townsquare-avatar--book svg g.arm-r line.limb { opacity: 0; }
.townsquare-avatar--book svg g.arm-r line.book-stub { opacity: 1; }
.townsquare-avatar--book svg g.elbow-r { opacity: 0; }

.townsquare-avatar svg g.book { opacity: 0; transition: opacity 140ms ease, transform 140ms ease; }
/* Tilted toward the head/to the side rather than held flat-on. */
.townsquare-avatar--book svg g.book { opacity: 1; transform: rotate(-10deg); }

/* umbrella: every figure opens one while the thunderstorm plays. Driven purely
   by the weather class on the widget root (weather.mjs), so self and peers all
   raise umbrellas together with no per-avatar state or protocol traffic.

   The umbrella (g.umbrella, figure.mjs) is a shaft at x=8.5 with a filled
   canopy dome closed over the head — the canopy extends above the viewBox top
   (negative y), which the figure svg's overflow:visible renders fine. The
   left arm holds it: grip on the shaft at (8.5,21), and like the book pose
   the shoulder/elbow angles are IK-solved for that grip (upper arm 6.85u,
   forearm 6.16u; the elbow-out-to-the-side branch reads as "holding something
   up" where the other branch crosses the chest). `animation: none` on the
   holding arm keeps it from swinging while walking — the legs keep the walk
   cycle — and its transition eases the arm up when the storm starts.

   Book pose owns both arms and hides elbow-r, so a reading figure skips the
   umbrella (they're off reading elsewhere anyway); away (zZz) figures skip it
   too. Placed after the book rules to keep the pose-precedence story in one
   spot, though the :not() guards are what actually resolve the conflict. */
.townsquare--weather-storm .townsquare-avatar:not(.townsquare-avatar--book, .townsquare-avatar--reading-away) svg g.arm-l {
  transform: rotate(31deg);
  animation: none !important;
  transition: transform 400ms ease;
}

.townsquare--weather-storm .townsquare-avatar:not(.townsquare-avatar--book, .townsquare-avatar--reading-away) svg g.elbow-l {
  transform: rotate(-99deg);
  animation: none !important;
  transition: transform 400ms ease;
}

.townsquare-avatar svg g.umbrella { opacity: 0; transition: opacity 300ms ease; }
.townsquare-avatar svg g.umbrella-canopy path { fill: currentColor; }

.townsquare--weather-storm .townsquare-avatar:not(.townsquare-avatar--book, .townsquare-avatar--reading-away) svg g.umbrella {
  opacity: 1;
}

/* The open: the canopy unfurls out of the shaft with a slight overshoot each
   time the storm class lands (including for visitors arriving mid-storm). */
.townsquare--weather-storm .townsquare-avatar:not(.townsquare-avatar--book, .townsquare-avatar--reading-away) svg g.umbrella-canopy {
  animation: presence-umbrella-open 520ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
}

@keyframes presence-umbrella-open {
  from { transform: scale(0.2, 0.35); }
  62% { transform: scale(1.1, 1.05); }
  to { transform: scale(1, 1); }
}

@media (prefers-reduced-motion: reduce) {
  .townsquare--weather-storm .townsquare-avatar svg g.umbrella-canopy { animation: none; }
}

/* ── Ghost stack: history lives in the scene as fading bubbles above the figure ── */
.townsquare-avatar__above {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 12px);
  /* --bubble-shift: collision-avoidance nudge; --bubble-scale / --bubble-fade:
     proximity prominence — all set per frame by bubble-layout */
  transform: translateX(calc(-50% + var(--bubble-shift, 0px))) scale(var(--bubble-scale, 1));
  transform-origin: bottom center;
  opacity: var(--bubble-fade, 1);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: max-content;
  max-width: min(240px, 72cqw);
  pointer-events: none;
  transition: opacity 140ms ease, transform 160ms ease;
}

/* Narrow stages (phones / small embeds): cap bubbles tighter so a long line
   wraps to more rows (taller) instead of eating the width — that lets the
   column solver pack more speakers side by side. Short lines are unaffected
   (width: max-content). Container query, so it tracks the widget's own width. */
@container (max-width: 460px) {
  .townsquare-avatar__above {
    max-width: min(190px, 52cqw);
  }
}

.townsquare-avatar__above:empty { display: none; }

/* hovering to read history yields the live stack to the tray */
.townsquare-avatar--has-history:hover .townsquare-avatar__above,
.townsquare-avatar--has-history:focus-within .townsquare-avatar__above,
.townsquare-avatar--tray-open .townsquare-avatar__above { opacity: 0; }

.townsquare-avatar__bubble {
  position: relative;
  max-width: 100%;
  padding: 11px 16px 12px;
  border-radius: var(--r);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-speech);
  line-height: 1.28;
  font-weight: var(--fw-speech);
  letter-spacing: -0.005em;
  box-shadow: var(--bshadow);
  transform-origin: bottom center;
  transition: opacity 280ms ease, transform 280ms ease, box-shadow 280ms ease;
}

.townsquare-avatar__bubble-text {
  display: block;
  overflow-wrap: anywhere;
}

.townsquare-avatar__tail {
  position: absolute;
  left: 50%;
  bottom: -11px;
  width: 160px;
  height: 12px;
  background: var(--surface);
  /* The base slides along the bubble's flat bottom (--tail-shift) and the tip
     leans the rest of the way to the speaker (--tail-tip), so the pointer
     always lands on whoever spoke even when the column was nudged aside. */
  transform: translateX(calc(-50% + var(--tail-shift, 0px)));
  clip-path: polygon(
    calc(50% - 8px) 0,
    calc(50% + 8px) 0,
    calc(50% + var(--tail-tip, 0px) + 1.5px) 100%,
    calc(50% + var(--tail-tip, 0px) - 1.5px) 100%
  );
  filter: drop-shadow(2px 3px 2px rgba(60, 40, 20, 0.14));
  transition: transform 160ms ease, clip-path 160ms ease;
}

/* only the live (bottom) bubble keeps its tail */
.townsquare-avatar__bubble--ghost .townsquare-avatar__tail { display: none; }

.townsquare-avatar__bubble--ghost {
  opacity: 0.72;
  transform: scale(0.92);
  box-shadow: none;
}

.townsquare-avatar__bubble--ghost.townsquare-avatar__bubble--far {
  opacity: 0.54;
  transform: scale(0.88);
}

.townsquare--expanded .townsquare-avatar__bubble--ghost {
  opacity: 0.82;
  transform: scale(0.94);
}

.townsquare--expanded .townsquare-avatar__bubble--ghost.townsquare-avatar__bubble--far {
  opacity: 0.66;
  transform: scale(0.9);
}

/* each line fades out on its own timer, oldest first */
.townsquare-avatar__bubble--expiring {
  opacity: 0;
  transform: scale(0.8);
}

/* ── History tray: hover a character to recover their recent lines ── */
.townsquare-avatar__tray {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 12px);
  z-index: 3;
  width: max-content;
  min-width: min(220px, 88cqw);
  max-width: min(360px, 88cqw);
  max-height: min(280px, 58vh);
  padding: 5px 4px;
  border-radius: 16px;
  background: var(--surface);
  box-shadow: var(--tray-shadow);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
  opacity: 0;
  pointer-events: none;
  transform: translate(calc(-50% + var(--tray-shift, 0px)), 4px);
  transition: opacity 140ms ease, transform 140ms ease;
}

.townsquare-avatar--has-history:hover .townsquare-avatar__tray,
.townsquare-avatar--has-history:focus-within .townsquare-avatar__tray,
.townsquare-avatar--tray-open .townsquare-avatar__tray {
  opacity: 1;
  pointer-events: auto;
  transform: translate(calc(-50% + var(--tray-shift, 0px)), 0);
}

/* composing: your tray would sit over neighbours' speech bubbles */
.townsquare-avatar--self.townsquare-avatar--composing .townsquare-avatar__tray,
.townsquare-avatar--self.townsquare-avatar--has-history:focus-within .townsquare-avatar__tray,
.townsquare-avatar--self.townsquare-avatar--has-history:has(.townsquare-avatar__below:hover) .townsquare-avatar__tray {
  opacity: 0;
  pointer-events: none;
  transform: translate(calc(-50% + var(--tray-shift, 0px)), 4px);
}

.townsquare-avatar--self.townsquare-avatar--composing .townsquare-avatar__above,
.townsquare-avatar--self.townsquare-avatar--has-history:focus-within .townsquare-avatar__above,
.townsquare-avatar--self.townsquare-avatar--has-history:has(.townsquare-avatar__below:hover) .townsquare-avatar__above {
  opacity: var(--bubble-fade, 1);
}

/* transparent bridge across the gap so hover survives figure → tray travel */
.townsquare-avatar__tray::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: 16px;
}

.townsquare-avatar__tray-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  column-gap: 14px;
  row-gap: 3px;
  padding: 8px 13px;
}

.townsquare-avatar__tray-row + .townsquare-avatar__tray-row {
  box-shadow: 0 -1px 0 var(--hair);
}

.townsquare-avatar__tray-msg {
  min-width: 0;
  white-space: normal;
  overflow-wrap: anywhere;
  line-height: 1.28;
  font-size: var(--fs-history);
  font-weight: var(--fw-history);
  color: var(--ink);
}

.townsquare-avatar__tray-time {
  font-size: var(--fs-time);
  font-weight: var(--fw-time);
  line-height: 1.28;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}

@media (max-width: 420px) {
  .townsquare-avatar__tray-row {
    grid-template-columns: minmax(0, 1fr);
  }

  .townsquare-avatar__tray-time {
    justify-self: start;
  }
}

/* ── Nameplate / composer: the persistent way in, below your figure ── */
.townsquare-avatar__below {
  position: absolute;
  left: 50%;
  top: calc(100% + 9px);
  /* --label-shift de-conflicts overlapping name tags (see layoutNameLabels in
     bubble-layout.mjs), so no tag — including your own — covers another. */
  transform: translateX(calc(-50% + var(--label-shift, 0px)));
  /* --label-fade recedes distant tags so crowded narrow stages stay legible;
     near 1 on wide stages (see layoutNameLabels). */
  opacity: var(--label-fade, 1);
  transition: opacity 140ms ease;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  white-space: nowrap;
}

/* Hovering or focusing a faded distant character restores its tag to full. */
.townsquare-avatar:hover .townsquare-avatar__below,
.townsquare-avatar:focus-within .townsquare-avatar__below {
  opacity: 1;
}

.townsquare-avatar__below[hidden] { display: none; }

.townsquare-avatar__below--peer {
  top: calc(100% + 7px);
  pointer-events: auto;
}

.townsquare-avatar--peer.townsquare-avatar--has-reading:not(.townsquare-avatar--has-display-name):not(:hover):not(:focus-within):not(.townsquare-avatar--label-open) .townsquare-avatar__below {
  display: none;
}

.townsquare-avatar__plate-row {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.townsquare-avatar__plate,
.townsquare-avatar__plate-chat {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.townsquare-avatar__plate {
  position: relative;
}

.townsquare-avatar__plate::after,
.townsquare-avatar__self-id::after {
  content: "";
  position: absolute;
  top: 0;
  right: -46px;
  width: 46px;
  height: 100%;
  z-index: 0;
}

.townsquare-avatar__plate-chat {
  padding: 6px 13px 6px 11px;
  border: 0;
  border-radius: 11px;
  background: var(--surface);
  box-shadow: var(--bshadow);
  cursor: pointer;
  font-family: var(--font);
  transition: transform var(--ts-transition-short) ease, box-shadow var(--ts-transition-short) ease;
}

.townsquare-avatar__plate:hover .townsquare-avatar__plate-chat { transform: translateY(-1px); }
.townsquare-avatar__plate-chat:focus-visible,
.townsquare-avatar__color-button:focus-visible,
.townsquare-avatar__swatch:focus-visible {
  outline: 2px solid var(--you);
  outline-offset: 2px;
}

.townsquare-avatar__plate[hidden],
.townsquare-avatar__profile-button[hidden] { display: none; }

.townsquare-avatar__profile-button[aria-expanded="true"] {
  color: var(--ink);
  border-color: var(--hair-2);
  background: var(--surface);
}

.townsquare-avatar__plate-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--you);
  transition: transform 160ms ease, width 160ms ease, height 160ms ease;
}

.townsquare-avatar__plate-name {
  font-size: var(--fs-name);
  font-weight: var(--fw-name);
  letter-spacing: 0.01em;
  color: var(--you);
  text-transform: lowercase;
}

.townsquare-avatar__peer-label {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  max-width: 140px;
  padding: 4px 9px;
  border-radius: 8px;
  background: color-mix(in oklab, var(--surface) 82%, transparent);
  color: currentColor;
  box-shadow: 0 6px 12px -10px rgba(60, 40, 20, 0.34);
  overflow: hidden;
}

.townsquare-avatar__peer-name-row {
  display: inline-flex;
  align-items: baseline;
  max-width: 100%;
  min-width: 0;
}

.townsquare-avatar__peer-name {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 12.5px;
  font-weight: var(--fw-name);
  text-transform: lowercase;
}

.townsquare-avatar__peer-name[hidden] { display: none; }

/* Owner badge: a server-verified crown that ordinary visitors cannot fake. */
.townsquare-avatar__owner-crown {
  flex-shrink: 0;
  margin-right: 4px;
  font-size: 0.95em;
  /* Render the emoji in its own colors even when the name is tinted. */
  -webkit-text-fill-color: initial;
  cursor: default;
}

.townsquare-avatar__owner-crown[hidden] { display: none; }

.townsquare-avatar__owner-crown:focus-visible {
  outline: 3px solid color-mix(in oklab, var(--you) 48%, transparent);
  outline-offset: 2px;
  border-radius: 4px;
}

.townsquare-avatar__owner-role {
  display: none;
  align-self: stretch;
  max-width: 100%;
  overflow: hidden;
  color: var(--ink-faint);
  font-size: 10.5px;
  font-weight: 620;
  letter-spacing: 0;
  line-height: 1.15;
  text-align: center;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.townsquare-avatar__owner-role[hidden] { display: none; }

.townsquare-avatar--owner.townsquare-avatar--peer:has(.townsquare-avatar__owner-crown:hover) .townsquare-avatar__owner-role,
.townsquare-avatar--owner.townsquare-avatar--peer:has(.townsquare-avatar__owner-crown:focus-visible) .townsquare-avatar__owner-role {
  display: block;
}

.townsquare-avatar--owner.townsquare-avatar--peer:has(.townsquare-avatar__owner-crown:hover) .townsquare-avatar__reading,
.townsquare-avatar--owner.townsquare-avatar--peer:has(.townsquare-avatar__owner-crown:focus-visible) .townsquare-avatar__reading {
  display: none !important;
}

.townsquare-avatar--owner .townsquare-avatar__peer-label,
.townsquare-avatar--owner .townsquare-avatar__plate {
  background: var(--owner-badge-bg, color-mix(in oklab, var(--surface) 70%, gold 14%));
  box-shadow: 0 6px 14px -9px rgba(150, 110, 20, 0.5);
}

.townsquare-avatar__reading {
  display: none;
  align-items: baseline;
  gap: 3px;
  align-self: stretch;
  max-width: 100%;
  min-width: 0;
  overflow: hidden;
  color: var(--ink-faint);
  font-size: 10.5px;
  font-weight: 620;
  letter-spacing: 0;
  line-height: 1.15;
  text-decoration: none;
}

.townsquare-avatar--peer:hover .townsquare-avatar__reading--available,
.townsquare-avatar--peer:focus-within .townsquare-avatar__reading--available,
.townsquare-avatar--label-open .townsquare-avatar__reading--available {
  display: inline-flex;
}

.townsquare-avatar__reading-prefix {
  flex: 0 0 auto;
  font-weight: 520;
}

.townsquare-avatar__reading-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.townsquare-avatar__reading:hover,
.townsquare-avatar__reading:focus-visible {
  color: currentColor;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.townsquare-avatar__reading[hidden] { display: none; }

.townsquare-avatar__plate-hint {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-faint);
}

.townsquare-avatar__profile {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-width: 142px;
}

.townsquare-avatar__profile[hidden] { display: none; }

.townsquare-avatar__profile-input {
  min-width: 0;
  width: 112px;
  height: 30px;
  padding: 0 2px;
  border: 1px solid var(--hair);
  border-radius: 7px;
  outline: 0;
  background: color-mix(in oklab, var(--surface-2) 82%, transparent);
  color: var(--ink);
  font-family: var(--font);
  font-size: 13px;
  font-weight: 520;
}

.townsquare-avatar__profile-input::placeholder { color: var(--ink-faint); }

.townsquare-avatar__color-button {
  width: 24px;
  height: 24px;
  padding: 0;
  border: 2px solid var(--surface);
  border-radius: 999px;
  background: var(--swatch, var(--you));
  box-shadow: 0 0 0 1px var(--hair), inset 0 0 0 1px rgba(42, 41, 38, 0.18);
  cursor: pointer;
  animation: townsquare-color-dot-grow 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.townsquare-avatar__color-button:hover,
.townsquare-avatar__color-button[aria-expanded="true"] {
  box-shadow: 0 0 0 2px var(--ink), inset 0 0 0 1px rgba(42, 41, 38, 0.18);
}

.townsquare-avatar__color-menu {
  position: absolute;
  z-index: 2;
  bottom: calc(100% + 10px);
  left: 12px;
  padding: 8px;
  border-radius: 9px;
  background: var(--surface);
  box-shadow: var(--tray-shadow);
  transform: translateX(-50%);
}

.townsquare-avatar__color-menu[hidden] { display: none; }

.townsquare-avatar__swatches {
  display: grid;
  grid-template-columns: repeat(4, 20px);
  gap: 6px;
}

.townsquare-avatar__swatch {
  width: 20px;
  height: 20px;
  padding: 0;
  border: 2px solid transparent;
  border-radius: 999px;
  background: var(--swatch);
  box-shadow: inset 0 0 0 1px rgba(42, 41, 38, 0.18);
  cursor: pointer;
}

.townsquare-avatar__swatch[aria-pressed="true"] {
  border-color: var(--surface);
  box-shadow: 0 0 0 2px var(--ink), inset 0 0 0 1px rgba(42, 41, 38, 0.18);
}


.townsquare-avatar__profile-button {
  position: absolute;
  z-index: 1;
  top: 50%;
  left: calc(100% + 8px);
  opacity: 0;
  pointer-events: none;
  transform: translate(-4px, -50%);
  transition: opacity var(--ts-transition-short) ease, transform var(--ts-transition-short) ease;
}

.townsquare-avatar__profile-button svg {
  width: 13px;
  height: 13px;
}

.townsquare-avatar__self-id:hover .townsquare-avatar__profile-button,
.townsquare-avatar__self-id:focus-within .townsquare-avatar__profile-button,
.townsquare-avatar__plate:hover .townsquare-avatar__profile-button,
.townsquare-avatar__plate:focus-within .townsquare-avatar__profile-button {
  opacity: 1;
  pointer-events: auto;
  transform: translate(0, -50%);
}

@media (pointer: coarse) {
  .townsquare-avatar__profile-button {
    opacity: 1;
    pointer-events: auto;
    transform: translate(0, -50%);
  }

  .townsquare-avatar__profile-button svg {
    width: 14px;
    height: 14px;
  }
}

.townsquare-avatar--profile-open .townsquare-avatar__self-id {
  gap: 7px;
}

.townsquare-avatar--profile-open .townsquare-avatar__plate::after,
.townsquare-avatar--profile-open .townsquare-avatar__self-id::after {
  display: none;
}

.townsquare-avatar--profile-open .townsquare-avatar__self-id > :is(.townsquare-avatar__plate-dot, .townsquare-avatar__owner-crown, .townsquare-avatar__plate-name, .townsquare-avatar__profile-button),
.townsquare-avatar--profile-open .townsquare-avatar__plate > :is(.townsquare-avatar__plate-chat, .townsquare-avatar__profile-button) {
  display: none;
}

@keyframes townsquare-color-dot-grow {
  from {
    transform: scale(0.36);
  }
  to {
    transform: scale(1);
  }
}

.townsquare-avatar__composer {
  position: relative;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 7px 7px 19px;
  min-width: 300px;
  max-width: min(340px, 80cqw);
  border-radius: 26px;
  background: var(--surface);
  box-shadow: var(--tray-shadow);
}

.townsquare-avatar__composer[hidden] { display: none; }

/* Slow-mode "Wait Ns…" caption, floating just above the composer pill. */
.townsquare-avatar__composer-hint {
  position: absolute;
  left: 19px;
  bottom: calc(100% + 6px);
  margin: 0;
  padding: 3px 10px;
  border-radius: 12px;
  background: var(--surface);
  box-shadow: var(--tray-shadow);
  color: var(--ink-soft);
  font-family: var(--font);
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  pointer-events: none;
}

.townsquare-avatar__composer-hint[hidden] { display: none; }

.townsquare-avatar__input {
  flex: 1;
  min-width: 0;
  min-height: 30px;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--ink);
  font-family: var(--font);
  font-size: 17px;
  font-weight: 460;
}

.townsquare-avatar__input::placeholder { color: var(--ink-faint); }

.townsquare-avatar__send {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--ink-soft);
  box-shadow: inset 0 0 0 1px var(--hair);
  cursor: pointer;
  transition: background 140ms ease, color 140ms ease, box-shadow 140ms ease;
}

.townsquare-avatar__send svg { width: 17px; height: 17px; display: block; }

.townsquare-avatar__send--ready {
  background: var(--you);
  color: #fff;
  box-shadow: none;
}

@media (max-width: 640px) {
  .townsquare__status { flex-direction: column; gap: 4px; }
  .townsquare-avatar__composer { min-width: min(260px, 80cqw); }
}

/* TOOLBAR: fixed bar at action-zone bottom (replaces floating composer).
   Always visible, houses composer input and action buttons.
   Figure keeps compact identity; peer nameplates never covered; virtual
   keyboard can't clip any UI. Touch-optimized with 42px target sizes. */
.townsquare__toolbar {
  position: absolute;
  z-index: var(--ts-z-ui-above);
  left: var(--ts-toolbar-inset);
  right: var(--ts-toolbar-inset);
  bottom: var(--ts-toolbar-bottom);
  display: flex;
  align-items: center;
  gap: var(--ts-toolbar-gap);
}

/* Actions (jump / high-five) are docked into the toolbar, so the floating
   container that once held them bottom-right is always empty. */
.townsquare__actions {
  display: none;
}

/* The docked composer fills the bar instead of floating on its own.
   Maintains its visual styling (background and shadow) while filling the toolbar.
   Reduce padding to fit within the action-zone without overflow. */
.townsquare__toolbar .townsquare-avatar__composer--docked {
  position: static;
  flex: 1 1 auto;
  min-width: 0;
  max-width: none;
  padding: 3px 7px 3px 19px;
}

/* Compact identity under the figure: display name, or "you" when unset. */
.townsquare-avatar__self-id {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 5px 11px;
  border-radius: 11px;
  background: var(--surface);
  box-shadow: var(--bshadow);
}

/* In expanded (fixed) mode the page can't scroll, so ride above the keyboard. */
.townsquare--expanded .townsquare__toolbar {
  bottom: calc(7px + var(--ts-keyboard, 0px));
}

@media (pointer: coarse) {
  .townsquare__button--sm {
    width: 40px;
    height: 40px;
  }

  .townsquare__button--sm svg {
    width: 18px;
    height: 18px;
  }

  .townsquare__status {
    right: 152px;
  }

  /* fingers need an even wider tap zone than mouse pointers */
  .townsquare-avatar--peer::before {
    inset: -10px -20px;
  }
}
